Skip to content

Add allOf support for model definitions (#98) #312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

bowenwr
Copy link
Contributor

@bowenwr bowenwr commented Jan 22, 2021

Fixes #98. Collapses the child elements into one, without class hierarchy, mixins, etc.

Collapses the child elements into one, without class heirarchy, mixins, etc.
@bowenwr bowenwr force-pushed the benchling-allof-support branch from c7bc39e to a7b6d47 Compare January 22, 2021 17:30
@codecov
Copy link

codecov bot commented Jan 22, 2021

Codecov Report

Merging #312 (a7b6d47) into main (aa6972c) will decrease coverage by 0.34%.
The diff coverage is 91.93%.

Impacted file tree graph

@@             Coverage Diff             @@
##              main     #312      +/-   ##
===========================================
- Coverage   100.00%   99.65%   -0.35%     
===========================================
  Files           47       47              
  Lines         1385     1439      +54     
===========================================
+ Hits          1385     1434      +49     
- Misses           0        5       +5     
Impacted Files Coverage Δ
..._python_client/parser/properties/model_property.py 92.18% <87.17%> (-7.82%) ⬇️
...penapi_python_client/parser/properties/__init__.py 100.00% <100.00%> (ø)
openapi_python_client/parser/properties/schemas.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update aa6972c...a7b6d47. Read the comment docs.

@bowenwr bowenwr marked this pull request as ready for review January 22, 2021 17:34
@bowenwr
Copy link
Contributor Author

bowenwr commented Jan 22, 2021

@dbanty @emann Although this PR is currently failing code coverage checks, I wanted to at least get some preliminary feedback on the allOf support we implemented in our fork. I believe this is the last major item that we have yet to upstream. 😁

FYI @packyg @dtkav @forest-benchling

Copy link
Collaborator

@dbanty dbanty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for contributing this upstream! I left a smattering of comments around about style/performance, but I think there might be an overall more elegant solution. Is there a reason we can't just look for the references in schemas in build_model_property? Then return a PropertyError if it's not found? I think the loop in build_schemas would then catch circular dependencies (which would just infinite loop with this implementation I think) and eventually resolve everything.

That should also clear up a bunch of logic around the recursion, optional vs required stuff, forward references, etc. and prevent folks from accidentally forgetting to resolve_references.

I could totally be missing some issue with that approach in here, maybe I'm making it sound simpler than it actually is 😅 but let me know.

if not isinstance(data, oai.Reference) and data.allOf:
for sub_prop in data.allOf:
if isinstance(sub_prop, oai.Reference):
references += [sub_prop]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
references += [sub_prop]
references.append(sub_prop)

I'm not completely sure on the internals of how append vs + works, but I think this is probably faster since += reassigns the array.

Comment on lines +559 to +560
models = list(schemas.models.values())
for model in models:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
models = list(schemas.models.values())
for model in models:
for model in schemas.models.values():

I don't see a reason to construct a new list just for iterating.

@@ -486,8 +494,10 @@ def _property_from_data(
)
elif data.type == "array":
return build_list_property(data=data, name=name, required=required, schemas=schemas, parent_name=parent_name)
elif data.type == "object":
elif data.type == "object" or data.allOf:
return build_model_property(data=data, name=name, schemas=schemas, required=required, parent_name=parent_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you have to call resolve_references here in order to not miss any references in allOf from non-component schemas.

while self.references:
reference = self.references.pop()
source_name = Reference.from_ref(reference.ref).class_name
referenced_prop = components[source_name]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could get an unhandled KeyError here, need to check that the component actually exists and return a PropertyError so it's handled gracefully.

reference = self.references.pop()
source_name = Reference.from_ref(reference.ref).class_name
referenced_prop = components[source_name]
assert isinstance(referenced_prop, oai.Schema)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally better to cast than to assert if just providing type info. Although there actually is no guarantee that this is true, is there? So this would really have to be a runtime check and return PropertyError

Comment on lines +47 to +49
if isinstance(referenced_prop.required, Iterable):
for sub_prop_name in referenced_prop.required:
required_set.add(sub_prop_name)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if isinstance(referenced_prop.required, Iterable):
for sub_prop_name in referenced_prop.required:
required_set.add(sub_prop_name)
for sub_prop_name in referenced_prop.required or []:
required_set.add(sub_prop_name)

Just sticking with the same form we usually use for optional iterables. Although maybe making the schema be a Set with a default would be cleaner all around.

for sub_prop_name in referenced_prop.required:
required_set.add(sub_prop_name)

for key, (value, source_name) in (props or {}).items():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for key, (value, source_name) in (props or {}).items():
for key, (value, source_name) in props.items():

Doesn't look like props can me None to me

Comment on lines +58 to +65
if required:
self.required_properties.append(prop)
# Remove the optional version
new_optional_props = [op for op in self.optional_properties if op.name != prop.name]
self.optional_properties.clear()
self.optional_properties.extend(new_optional_props)
elif not any(ep for ep in (self.optional_properties + self.required_properties) if ep.name == prop.name):
self.optional_properties.append(prop)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all makes me feel like we should make the properties lists sets instead, and make Property hashable on name? Or make them dicts keyed by name? Would save us a lot of cycles on lists when doing things like this.

@dbanty dbanty added this to the 0.8.0 milestone Feb 1, 2021
@dbanty
Copy link
Collaborator

dbanty commented Feb 1, 2021

Is there a reason we can't just look for the references in schemas in build_model_property?

Mentioned in the PR as well, but I attempted this in #321 to show what I was talking about, so you may want to check that before responding to any of my other comments in this PR.

@dbanty dbanty removed this from the 0.8.0 milestone Feb 19, 2021
@dbanty
Copy link
Collaborator

dbanty commented Mar 12, 2021

Closing in favor of #321

@dbanty dbanty closed this Mar 12, 2021
@eli-bl eli-bl deleted the benchling-allof-support branch November 22, 2024 19:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support allOf in response schemas
3 participants